package org.haptimap.hcimodules.util;

import org.haptimap.hcimodules.HCIModule;
import org.haptimap.hcimodules.util.MagneticCompass.CompassListener;
import org.haptimap.hcimodules.util.MyLocationModule.MyLocationListener;

import android.content.Context;
import android.location.Location;
import android.util.Log;

/**
 * Help class that provides both location and bearings.
 * The main idea with this class is to provide the most common values 
 * when trying to find an orientation based on the current location and
 * the azimuth provided by the magnetic compass. 
 * 
 * @author Miguel Molina, July 2011
 */
public class OrientationModule extends HCIModule{

	private static final String TAG = "OrientationModule";
	private static final boolean DEBUG = false;

	protected MyLocationModule mMyLocationModule;
	protected MagneticCompass mMagneticCompass;
	protected Location mCurrentLocation;
	protected float mAzimuth;
	protected float mAzimuth360;
	protected MyLocationListener mMyLocationListener;
	protected CompassListener mCompassListener;
	protected float[] mOrientationValues;
	private boolean compassRunning;
	private boolean locationRunning;

	public OrientationModule(Context context) {
		super(context);
		mMyLocationModule = new MyLocationModule(context);
		locationRunning = false;
		mMagneticCompass = new MagneticCompass(context);
		compassRunning = false;
		mAzimuth = mAzimuth360 = Float.NaN;
		mOrientationValues = new float[3];
	}

	@Override
	public void onStart() {
		startMyLocation();
		startMyCompass();
	}

	@Override
	public void onPause() {
		pauseMyLocation();
		pauseMyCompass();
	}

	@Override
	public void onResume() {
		resumeMyLocation();
		resumeMyCompass();
	}

	@Override
	public void onStop() {
		stopMyLocation();
		stopMyCompass();
	}

	@Override
	public void onDestroy() {
		destroyMyLocation();
		destroyMyCompass();
	}

	private void startMyLocation() {
		if(mMyLocationModule != null){
			registerMyLocationListener();
			mMyLocationModule.onStart();
			locationRunning = true;
		}
		if(DEBUG) Log.i(TAG, "startMyLocation()");
	}

	private void pauseMyLocation() {
		if(mMyLocationModule != null){
			mMyLocationModule.onPause();
			locationRunning = false;	
			unregisterMyLocationListener();
		}
		if(DEBUG) Log.i(TAG, "pauseMyLocation()");
	}	

	private void registerMyLocationListener() {
		mMyLocationListener = new MyLocationModule.MyLocationListener() {

			public void onMyLocationChanged(Location location) {
				OrientationModule.this.mCurrentLocation = location;
			}
		};
		mMyLocationModule.setOnMyLocationListener(mMyLocationListener);
		if(DEBUG) Log.i(TAG, "registerMyLocationListener()");
	}

	private void unregisterMyLocationListener(){
		mMyLocationModule.unregisterListener();
		mMyLocationListener = null;
		mCurrentLocation = null;
	}

	private void stopMyLocation() {
		if(mMyLocationModule != null){
			mMyLocationModule.onStop();
			unregisterMyLocationListener();
			locationRunning = false;
		}
		if(DEBUG) Log.i(TAG, "stopMyLocation()");
	}

	private void resumeMyLocation() {
		if(mMyLocationModule != null){
			mMyLocationModule.onResume();
			registerMyLocationListener();
			locationRunning = true;
		}
		if(DEBUG) Log.i(TAG, "resumeMyLocation()");
	}

	private void destroyMyLocation() {
		if(mMyLocationModule != null){
			unregisterMyLocationListener();
			mMyLocationModule.onDestroy();
		}
		mMyLocationModule = null;
		if(DEBUG) Log.i(TAG, "destroyMyLocation()");
	}	

	private void startMyCompass() {
		if(mMagneticCompass != null){
			registerMyCompassListener();
			mMagneticCompass.onStart();
			compassRunning = true;
			if(DEBUG) Log.i(TAG, "startMyCompass()");
		}
	}

	private void registerMyCompassListener() {
		mCompassListener = new MagneticCompass.CompassListener() {

			public void onCompassChanged(float[] values) {
				OrientationModule.this.mOrientationValues = values.clone();
				OrientationModule.this.mAzimuth = values[0];
				OrientationModule.this.mAzimuth360 = (360 + values[0])%360;
			}
		};
		mMagneticCompass.setOnCompassListener(mCompassListener);
		if(DEBUG) Log.i(TAG, "registerMyCompassListener()");
	}

	private void unregisterMyCompassListener(){
		mMagneticCompass.unregisterListener();
		mCompassListener = null;		
		mAzimuth = mAzimuth360 = Float.NaN;
	}

	private void stopMyCompass() {
		if(mMagneticCompass != null){
			mMagneticCompass.onStop();
			if(mCompassListener != null){			
				unregisterMyCompassListener();
			}
			compassRunning = false;
		}
		if(DEBUG) Log.i(TAG, "stopMyCompass()");
	}

	private void pauseMyCompass() {
		if(mMagneticCompass != null){
			mMagneticCompass.onPause();
			compassRunning = false;
			if(mCompassListener != null){			
				unregisterMyCompassListener();
			}
		}
		if(DEBUG) Log.i(TAG, "pauseMyCompass()");
	}

	private void resumeMyCompass() {
		if(mMagneticCompass != null){
			mMagneticCompass.onResume();
			registerMyCompassListener();
			compassRunning = true;
		}
		if(DEBUG) Log.i(TAG, "resumeMyCompass()");
	}

	private void destroyMyCompass() {
		if(mMagneticCompass != null){
			if(mCompassListener != null){			
				unregisterMyCompassListener();
			}
			mMagneticCompass.onDestroy();
			compassRunning = false;
		}
		mMagneticCompass = null;
		if(DEBUG) Log.i(TAG, "destroyMyCompass()");
	}

	/**
	 * @return whether this module is running.
	 */
	public boolean isRunning(){
		if(DEBUG) Log.i(TAG, "compassStarted()");
		return new Boolean(compassRunning && locationRunning);
	}

	/**
	 * A representation of the azimuth in a half circle representation.
	 * The values are between +(-) 180 degrees.
	 * 
	 * @return The current value as a +(-) half circle representation
	 */
	public float getAzimuth(){
		return this.mAzimuth;
	}

	/**
	 * A representation of the azimuth in a full 360 degrees circle.
	 * @return The current azimuth
	 */
	public float getAzimuth360(){
		return this.mAzimuth360;
	}

	/**
	 * The current position represented as a {@link Location}
	 * @return The {@link Location} representing out current position.
	 */
	public Location getCurrentLocation(){
		return new Location(mCurrentLocation);
	}

	/**
	 * An array containing the orientation values: azimuth, pitch and roll
	 * 
	 * @return 
	 * <ul>
	 * <li>orientationValues[0]: azimuth, rotation around the Z axis.
	 * <li>orientationValues[1]: pitch, rotation around the X axis.
	 * <li>orientationValues[2]: roll, rotation around the Y axis.
	 * </ul> 
	 */
	public float[] getOrientationValues(){
		return mOrientationValues.clone();
	}

}
